package com.keya.pacs;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.keya.base.enums.SyncStudiesStatus;
import com.keya.base.output.ResultData;
import com.keya.device.Device;
import com.keya.device.DeviceService;
import com.keya.features.tool.*;
import com.keya.pull.PullStudyRecord;
import com.keya.pull.PullStudyRecordService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang.StringUtils;
import org.dcm4che3.data.Keyword;
import org.dcm4che3.data.Tag;
import org.dcm4che3.util.TagUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

/**
 * .
 *
 * @author : xf.he
 * @version : 1.0 2023/11/2
 * @since : 1.0
 **/
@Slf4j
@Service
public class PacsService {

    @Resource
    DeviceService deviceService;
    @Resource
    private PullStudyRecordService pullStudyRecordService;

    /**
     * @param deviceId 设备ID
     */
    public List<PullStudyRecord> list(Integer deviceId, boolean pullFilterByStudyDate) throws Exception {
        Device device = deviceService.getDeviceById(deviceId);
        Device localDevice = deviceService.getLocalHost();
        DicomNetParams dicomNetParams = new DicomNetParams().convert(device).setCallingAE(localDevice.getAetitle());
        LocalDateTime now = LocalDateTime.now();
        int hour = Math.max(0, now.getHour() - 1);
        String findDate = now.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        List<FindResult> results;
        if (pullFilterByStudyDate) {
            Map<String, String> byDateFilter = DicomFindParamsBuilder.byModality("CT");
            Map<String, String> byDateTimeFilter = DicomFindParamsBuilder.byModality("CT");
            byDateFilter.put("StudyDate", findDate);
            byDateTimeFilter.put("StudyDate", findDate);
            byDateTimeFilter.put("StudyTime", String.format("%02d0000-", hour));
            List<FindResult> byDateTime = new FindSCU().cFind(dicomNetParams, byDateTimeFilter);
            List<FindResult> byDate = new FindSCU().cFind(dicomNetParams, byDateFilter);
            results = ListUtils.union(byDate, byDateTime);
        } else {
            results = new FindSCU().cFind(dicomNetParams, DicomFindParamsBuilder.byModality("CT"));
        }
        Set<String> newFinding = new HashSet<>();
        List<FindResult> findResults = results.stream()
                .filter(r -> StringUtils.startsWith(r.getString(Tag.StudyDate), findDate))
                .filter(r -> StringUtils.isNotEmpty(r.getString(Tag.StudyInstanceUID)) && newFinding.add(r.getString(Tag.StudyInstanceUID)))
                .collect(Collectors.toUnmodifiableList());
        if (CollectionUtils.isEmpty(findResults)) {
            return null;
        }
        Set<String> existed = new HashSet<>();
        List<String> newFindingList = new ArrayList<>(newFinding);
        for (int batchSize = 100, i = 0; i < newFindingList.size(); i += batchSize) {
            List<String> batch = newFindingList.subList(i, Math.min(i + batchSize, newFindingList.size()));
            List<String> listInDB = pullStudyRecordService.getBaseMapper().getExistedUIDs(batch);
            if (listInDB != null && listInDB.size() > 0) {
                existed.addAll(listInDB);
            }
        }
        return findResults.stream()
                .filter(finding ->
                        StringUtils.isNotEmpty(finding.getString(Tag.StudyInstanceUID)) &&
                                !existed.contains(finding.getString(Tag.StudyInstanceUID)))
                .map(finding ->
                        new PullStudyRecord()
                                .setDeviceId(device.getId())
                                .setStatus(SyncStudiesStatus.PENDING.getCode())
                                .setStudyInstanceUid(finding.getString(Tag.StudyInstanceUID))
                                .setStudyDescription(finding.getString(Tag.StudyDescription))
                                .setAccessionNumber(finding.getString(Tag.AccessionNumber))
                                .setStudyTime(finding.studyTimeLocalDateTime())
                                .setPatientName(finding.getString(Tag.PatientName))
                                .setCreateTime(LocalDateTime.now())
                )
                .collect(Collectors.toList());
    }


    private void processRecord(PullStudyRecord record, StudyDescriptionFilter filter, int[] backoff) {
        if (!filter.shouldExclude(record.getStudyDescription())) {
            MoveResult result = moveStudy(record);
            if (result != null) {
                log.info("moveResult status:{}, studyInstanceUid:{}, count:{}", result.getStatus(),
                        record.getStudyInstanceUid(), result.getNumberOfCompletedSuboperations());
            }
            boolean success = result != null && result.isSuccess();
            if (success) {
                record.setSuccessCount(record.getSuccessCount() + 1);
            } else {
                record.setFailCount(record.getFailCount() + 1);
            }

            int times = record.getSuccessCount() + record.getFailCount();
            if (LocalDateTime.now().minusMinutes(backoff[backoff.length - 1]).isAfter(record.getStudyTime()) ||
                    times >= backoff.length) {
                var status = success ? SyncStudiesStatus.SUCCESS : SyncStudiesStatus.FAILED;
                record.setStatus(status.getCode());
                record.setNextMoveTime(null);
            } else {
                int gap = backoff[backoff.length - 1];
                long delta = ChronoUnit.MINUTES.between(record.getStudyTime(), LocalDateTime.now());
                for (int i = 1; i < backoff.length; i++) {
                    if (backoff[i] > delta) {
                        gap = backoff[i];
                        break;
                    }
                }
                record.setNextMoveTime(record.getStudyTime().plusMinutes(gap));
            }
        } else {
            record.setStatus(SyncStudiesStatus.FAILED.getCode());
        }
        record.setLastMoveTime(LocalDateTime.now());
        pullStudyRecordService.updateById(record);
    }


    private MoveResult moveStudy(PullStudyRecord record) {
        try {
            log.warn("moving record {}", record.getAccessionNumber());
            DicomNetParams dicomNetParams = getDicomNetParams(record.getDeviceId());
            if (dicomNetParams == null) {
                return null;
            }
            if (!ensureStudyInstanceUID(record, dicomNetParams)) {
                return null;
            }
            return new MoveSCU().cMove(dicomNetParams, record.getStudyInstanceUid(), null);
        } catch (Exception e) {
            log.error("failed to move study", e);
        }
        return null;
    }

    private DicomNetParams getDicomNetParams(int deviceId) throws Exception {
        Device localDevice = deviceService.getLocalHost();
        Device device = deviceService.getDeviceById(deviceId);
        if (device == null) {
            log.error("找不到设备id：{}", deviceId);
            return null;
        }
        return new DicomNetParams().convert(device).setCallingAE(localDevice.getAetitle());
    }

    private boolean ensureStudyInstanceUID(PullStudyRecord pullStudyRecord, DicomNetParams dicomNetParams) {
        if (!StringUtils.isEmpty(pullStudyRecord.getStudyInstanceUid())) {
            return true;
        }
        FindSCU findSCU = new FindSCU();
        List<FindResult> findResults = findSCU.cFind(dicomNetParams,
                DicomFindParamsBuilder.byAccessionNumber(pullStudyRecord.getAccessionNumber()));
        if (findResults == null || findResults.isEmpty()) {
            return false;
        }
        FindResult findResult = findResults.get(0);
        log.info("findResult:" + findResult.toString());
        pullStudyRecord.setStudyInstanceUid(findResult.getString(Tag.StudyInstanceUID));
        return true;
    }


    public ResultData moveScu(int deviceId, String studyInstanceUID, String seriesInstanceUID) throws Exception {
        DicomNetParams dicomNetParams = getDicomNetParams(deviceId);
        if (dicomNetParams == null) {
            return ResultData.fail("找不到设备:" + deviceId);
        }
        MoveResult moveResult = new MoveSCU().cMove(dicomNetParams, studyInstanceUID, seriesInstanceUID);
        if (moveResult.isSuccess()) {
            return ResultData.success(moveResult);
        } else {
            return ResultData.fail("操作失败个数为：" + moveResult.getNumberOfFailedSuboperations());
        }
    }

    public ResultData storeScu(Device device, String path) throws Exception {
        Device localDevice = deviceService.getLocalHost();
        DicomNetParams dicomNetParams = new DicomNetParams().convert(device).setCallingAE(localDevice.getAetitle());
        if (dicomNetParams == null) {
            return ResultData.fail("找不到设备:" + device.getId());
        }
        dicomNetParams.setTimeout(6000);
        return new StoreSCU().cStore(dicomNetParams, path);
    }

    Set<String> wrongIncludeFields = new HashSet<>();

    public ResultData findScu(int deviceId, String[] filters,String studyInstanceUID) {
        try {
            DicomNetParams dicomNetParams = getDicomNetParams(deviceId);
            if (dicomNetParams == null) {
                return ResultData.fail("找不到设备:" + deviceId);
            }
            Map<String, String> queryMap = buildFilterMap(filters);
            queryMap.put(Keyword.valueOf(Tag.StudyInstanceUID), studyInstanceUID);
            List<FindStudyResult> findResults = new FindSCU().cFindStudy(dicomNetParams, queryMap, "SERIES");
            String message = "成功";
            if (ObjectUtil.isNotEmpty(wrongIncludeFields)) {
                message = String.format("成功，%s 参数有误", wrongIncludeFields.toString());
                wrongIncludeFields.clear();
            }
            return ResultData.success(findResults, message);
        } catch (Exception e) {
            e.printStackTrace();
            return ResultData.fail("失败:" + e.getMessage());
        }
    }

    public ResultData findScuStudy(int deviceId, String[] filters) {
        try {
            DicomNetParams dicomNetParams = getDicomNetParams(deviceId);
            if (dicomNetParams == null) {
                return ResultData.fail("找不到设备:" + deviceId);
            }
            Map<String, String> queryMap = buildFilterMap(filters);
            return getResultData(dicomNetParams, queryMap, "STUDY");
        } catch (Exception e) {
            e.printStackTrace();
            return ResultData.fail("失败:" + e.getMessage());
        }
    }

    private ResultData getResultData(DicomNetParams dicomNetParams, Map<String, String> queryMap, String l) throws Exception {
        List<FindStudyResult> findResults = new FindSCU().cFindStudy(dicomNetParams, queryMap, l);
        String message = "成功";
        if (ObjectUtil.isNotEmpty(wrongIncludeFields)) {
            message = String.format("成功，%s 参数有误", wrongIncludeFields.toString());
            wrongIncludeFields.clear();
        }
        return ResultData.success(buildStudy(findResults), message);
    }


    public List<StudyWithSeries> buildStudy(List<FindStudyResult> findResults) {
        List<StudyWithSeries> results = new ArrayList<>();
        Map<String, List<FindStudyResult>> grouped = findResults.stream().collect(Collectors.groupingBy(FindStudyResult::getStudyInstanceUID));
        // 遍历每个组
        for (Map.Entry<String, List<FindStudyResult>> entry : grouped.entrySet()) {
            // 获取这个组的第一个元素
            FindStudyResult first = entry.getValue().get(0);
            StudyWithSeries study = new StudyWithSeries();
            BeanUtil.copyProperties(first, study);
            // 将这个组的所有元素添加到series列表中
//            study.setSeries(entry.getValue());
            // 将这个对象添加到结果列表中
            results.add(study);
        }
        return results;

    }

    public Map<String, String> buildFilterMap(String[] filters) {
        Map<String, String> queryMapFilter = DicomFindParamsBuilder.initMap();
        if (ObjectUtil.isEmpty(filters)) {
            return queryMapFilter;
        }
        for (String s : filters) {
            String[] tmp = s.split("=");
            String key = tmp[0].substring(0, 1).toUpperCase() + tmp[0].substring(1);
            String value = tmp[1].trim();
            try {
                Tag.class.getField(key);
                queryMapFilter.put(key, value);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
                wrongIncludeFields.add(key);
            }
        }
        return queryMapFilter;
    }

    public Set<String> filterIncludeFields(String[] includeField, Map<String, String> queryMap) {
        Set<String> includeFields = new FindSCU().buildDefaultIncludeFields();
        if (ObjectUtil.isNotEmpty(includeField)) {
            for (String s : includeField) {
                s = s.trim();
                s = s.substring(0, 1).toUpperCase() + s.substring(1);
                int tag = TagUtils.forName(s);
                if (tag != -1) {
                    includeFields.add(s);
                } else {
                    // 如果用户输入或想查找的field是Attributes里面没有的
                    wrongIncludeFields.add(s);
                }
            }
        }
        if (ObjectUtil.isNotEmpty(queryMap)) {
            includeFields.addAll(queryMap.keySet());
        }
        return includeFields;
    }


}
